#include "Circ.h"

// dependency includes
#include "Rect.h"
#include "Line.h"

Circ::Circ():
Shape(),
radius(0) {

}

Circ::Circ(Transform* const position, float rad):
Circ(position, 0.f, 0.f, rad) {

}

Circ::Circ(Transform* const position, const Vector2D& posOffset, float rad):
Circ(position, posOffset.x, posOffset.y, rad) {

}

Circ::Circ(Transform* const position, float xOffset, float yOffset, float rad):
Shape(position, xOffset, yOffset),
radius(rad) {

}

Circ::~Circ() {

}

float Circ::SqDistanceWith(float x, float y) const {
	float dx = GetX() - x;
	float dy = GetY() - y;
	return dx * dx + dy * dy;
}

float Circ::SqDistanceWith(const Vector2D& p) const {
	return SqDistanceWith(p.x, p.y);
}

bool Circ::Contains(float x, float y) const {
	if (SqDistanceWith(x, y) <= GetRadiusSq()) {
		// if the square of the distance with the point is less than the square of the radius, it is inside the circle
		return true;
	}

	// otherwise, it isn't
	return false;
}

bool Circ::Contains(const Vector2D& p) const {
	return Contains(p.x, p.y);
}

bool Circ::CollidesWith(const Line* const l, Vector2D& hitOut) const {
	return l->CollidesWith(this, hitOut);
}

bool Circ::CollidesWith(const Rect* const r, Vector2D& hitOut) const {
	if (r->Contains(GetX(), GetY())) {
		// the center of the circle is inside the rectangle
		// in case of a small circle inside a big rectangle

		// set the hit location
		r->GetClosestPointTo(GetX(), GetY(), hitOut);

		return true;
	}

	if (Contains(r->GetX(), r->GetY())) {
		// the rectangle is inside the circle
		// in case of a small rectangle inside a big circle

		// set the hit location
		r->GetClosestPointTo(r->GetX(), r->GetY(), hitOut);

		return true;
	}

	// create a line segment for each of the sides of the rectangle
	Transform* dummyPos = new Transform();
	Vector2D dummyDir;
	Line* bottom = new Line(dummyPos, dummyDir);
	r->GetBottomLine(*bottom);
	Line* left = new Line(dummyPos, dummyDir);
	r->GetLeftLine(*left);
	Line* top = new Line(dummyPos, dummyDir);
	r->GetTopLine(*top);
	Line* right = new Line(dummyPos, dummyDir);
	r->GetRightLine(*right);
	
	// check collisions, no collision by default
	bool isColliding = false;

	// if any of the sides intersect with the circle, it means there is a collision
	if (bottom->CollidesWith(this, hitOut)) {
		isColliding = true;
	} else if (right->CollidesWith(this, hitOut)) {
		isColliding = true;
	} else if (top->CollidesWith(this, hitOut)) {
		isColliding = true;
	} else if (left->CollidesWith(this, hitOut)) {
		isColliding = true;
	}

	// clean up all the pointers
	delete top, left, bottom, right;
	delete dummyPos;

	return isColliding;
}

bool Circ::CollidesWith(const Circ* const c, Vector2D& hitOut) const {
	// sum of the radiuses
	float radSumSq = GetRadius() + c->GetRadius();
	// square it
	radSumSq *= radSumSq;

	// check the square of the distance between the two circles
	if (SqDistanceWith(c->GetX(), c->GetY()) <= radSumSq) {
		// if it is less than the square of the sum of the radiuses, they collide

		// cast a line from the center of the circle towards the center of this circle
		Vector2D start, end, dir;
		GetPosition(&end);
		c->GetPosition(&start);
		dir.Add(end);
		dir.Substract(start);
		Line l = Line(c->GetParent(), dir, c->GetRadius() + 0.01f);

		// set the hit location
		l.CollidesWith(this, hitOut);

		return true;
	}

	// otherwise, they don't
	return false;
}

bool Circ::CollidesWith(const Poly* const p, Vector2D& hitOut) const {
	// not implemented
	return false;
}

void Circ::Draw() const {
	// not implemented
	return;
}
void Circ::COut() const {
	std::cout << "Circle: (" << GetX() << ", " << GetY() << ") ---> Radius " << GetRadius() << std::endl;
	return;
}